// 这个包里的东西 包括一个默认的registry 一个注册和删除的一个服务
package registry

import (
	"bytes"
	"encoding/json"
	"fmt"
	"gcc/global"
	"log"
	"net/http"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
)

// 包级的变量，供我们进行存储运行中产生的信息
// TODO: 考虑存到redis中，同时保存到数据库中
var DefaultRegistry = registry{
	registrations: make([]Registration, 0),
	mutex:         new(sync.RWMutex),
}

type registry struct {
	registrations []Registration
	mutex         *sync.RWMutex
}

// 这个方法就是服务注册时，调用加到rigistry这个结构体中
func (r *registry) add(reg Registration) error {
	r.mutex.Lock()
	r.registrations = append(r.registrations, reg)
	r.mutex.Unlock()
	// 下面这个代码是主动给自己加依赖
	err := r.addRequiredService(reg)
	// 下面这个是被到加依赖，就是依赖后启动，我被我被告有这个服务了然后加上这个地址
	// TODO: 要想一想， 这个每次上线就是本机服务名子和地址，真的用切片吗？
	r.notify(patch{
		Add: []patchEntry{
			patchEntry{
				ServiceName: reg.ServiceName,
				ServiceURL:  global.Address(reg.ServiceURL),
			},
		},
	})
	return err
}

// 新人要仔细感觉这段代码，体会什么时候用go协和，为什么同样是处理依赖，在主动加依赖的时候不用协程
// 我的最直观的理解就是，当主动填加依赖的时候，最终服务器是发送给一个人
// 而被动通知的话，明显是给所有要这个依赖的服务，不会是一个人。
func (r *registry) notify(p patch) {
	r.mutex.RLock()
	for _, registration := range r.registrations {
		go func(reg Registration) {
			for _, regRequiredService := range reg.RequireService {
				sendFlag := false
				p := patch{Add: []patchEntry{}, Removed: []patchEntry{}}
				for _, sn := range p.Add {
					if regRequiredService == sn.ServiceName {
						p.Add = append(p.Add, sn)
						sendFlag = true
					}
				}
				for _, sn := range p.Removed {
					if regRequiredService == sn.ServiceName {
						p.Removed = append(p.Removed, sn)
						sendFlag = true
					}
				}

				if sendFlag {
					r.sendPatch(p, reg.RequiredUpdateService)
				}
			}
		}(registration)
	}
	r.mutex.RUnlock()
}

// 这个方法是从reigstry结构体中，删除掉某个结构体信息，也就是删除了该服务
// 先删除同一个服务的url，当url都消失的时候，把这个服务名也删除掉
func (r *registry) remove(url string) error {
	// fmt.Println(url)
	p := patchEntry{}
	r.mutex.Lock()
	// fmt.Println(r.registrations)
	for i, reg := range r.registrations {
		// fmt.Println("1")
		if reg.ServiceURL == url {
			// fmt.Println("2")
			// fmt.Println(r.registrations)
			p.ServiceName = r.registrations[i].ServiceName
			p.ServiceURL = global.Address(r.registrations[i].ServiceURL)
			r.registrations = append(r.registrations[:i], r.registrations[i+1:]...)

		}
	}
	r.mutex.Unlock()
	// 主动删除的时候， 要通知更改服务依赖
	r.notify(patch{
		Removed: []patchEntry{
			p,
		},
	})
	return nil
}

// 主动向注册服务请示requiredService并加到自己的requiredService中
func (r *registry) addRequiredService(reg Registration) error {
	r.mutex.RLock()
	var p patch
	for _, registration := range r.registrations {
		for _, requireService := range reg.RequireService {
			if requireService == registration.ServiceName {
				p.Add = append(p.Add, patchEntry{
					registration.ServiceName,
					global.Address(registration.ServiceURL),
				})
			}
		}
	}
	r.mutex.RUnlock()
	err := r.sendPatch(p, reg.RequiredUpdateService)
	if err != nil {
		log.Printf("sendPatch for update requireService's url faild, err: %v", err)
	}
	return nil

}

// 发给来注册的服务，告诉他，你依赖的服务有地址了，你记录一下
func (r *registry) sendPatch(p patch, url string) error {
	data, err := json.Marshal(p)
	if err != nil {
		return err
	}
reSend:
	resp, err := http.Post(url, "application/json", bytes.NewBuffer(data))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		time.Sleep(1 * time.Second)
		goto reSend
	}
	return nil
}

// =====================================================================================
// serviceHandler 服务函数！
type RegistryHandlerInterface interface {
	RegistryServiceHandler(c *gin.Context)
	GetServicesHandler(c *gin.Context)
}

type Registry struct{}

// 因为我们使用了gin框架，在路由的层面已经确定了，不是post或者不是delete请示，匹配不过来，所以不用
// 写default分支，而我们用switch case 语句不写这个服务，单纯是因为我不想多写几个函数！
func (r Registry) RegistryServiceHandler(c *gin.Context) {
	switch c.Request.Method {
	case http.MethodPost:
		var reg Registration
		err := c.ShouldBindJSON(&reg)
		if err != nil {
			log.Println("post request bind data failed")
			c.JSON(http.StatusInternalServerError, gin.H{"data": "bind data failed"})
			return
		}

		fmt.Println(reg)

		err = DefaultRegistry.add(reg)
		if err != nil {
			log.Printf("registry add service failed! err : %s\n", err.Error())
		}
		log.Printf("http://%s, registry success!\n", string(global.ServicesAddress[global.RegistryService]))
		//fmt.Println(DefaultRegistry)
		c.JSON(http.StatusOK, gin.H{
			"code": "200",
			"data": "registry success!",
		})

	case http.MethodDelete:
		var reg Registration
		err := c.ShouldBindJSON(&reg)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"data": "bind data failed"})
			return
		}
		err = DefaultRegistry.remove(reg.ServiceURL)
		if err != nil {
			log.Printf("registry remove url failed! err: %s\n", err.Error())
			return
		}
		log.Printf("http://%s, registry remove success!\n", string(global.ServicesAddress[global.RegistryService]))
		c.JSON(http.StatusOK, gin.H{
			"code": "200",
			"data": "remove success!",
		})
	default:
		c.JSON(http.StatusBadRequest, gin.H{"data": "request must be post!"})
		log.Printf("request must be post!\n")
		return
	}

}

// =====================================================================================

// -====================================================================================
// =====================heart service===================================================
// =====================================================================================
func (r *registry) heartbeat(freq time.Duration) {
	for {
		if len(r.registrations) == 0 {
			log.Println("没有服务进行注册")
			time.Sleep(freq * 3)
			continue
		}
		var wg sync.WaitGroup
		wg.Add(len(r.registrations))
		for _, registration := range r.registrations {

			go func(reg Registration) {
				defer wg.Done()
				success := true
				for attemps := 0; attemps < 3; attemps++ {
					resp, err := http.Get("http://"+reg.HeartService)
					if err != nil {
						success = false
						log.Printf("send heart to %s failed : %d \n", string(reg.ServiceName), attemps)
						time.Sleep(1 * time.Second)
					} else if resp.StatusCode == http.StatusOK {
						log.Println("服务存在")
						success = true
						break
					}
				}
				if !success {
					//  一旦心跳检测不到的时候 就说明已经断了，我们就是可将服务从defaultRegistration中删掉
					//  而一旦服务重新连接的时候，就会先注册，先加载自身依赖
					//  随后通知依赖本身的服务填加自己的地址
					r.remove(reg.ServiceURL)
				}

			}(registration)

		}
		wg.Wait()
		time.Sleep(freq)
	}

}

var once sync.Once

func RegistryHeartBegin() {
	once.Do(func() {
		go DefaultRegistry.heartbeat(3 * time.Second)
	})
}

func (r Registry) GetServicesHandler(c *gin.Context) {
	nameMapUrl := make(map[global.ServiceName]global.Address)
	serviceNames := c.QueryArray("servicename")
	for _, registraion := range DefaultRegistry.registrations {
		for _, name := range serviceNames {
			if string(registraion.ServiceName) == name {
				nameMapUrl[registraion.ServiceName] = global.Address(registraion.ServiceURL)
			}
		}
	}
	c.JSON(http.StatusOK, gin.H{
		"code": "ok",
		"data": nameMapUrl,
	})
}
